package com.goldman.distributed.common.ratelimit.aspect;

import com.goldman.distributed.common.ratelimit.annotation.RedissonRateLimiter;
import com.goldman.distributed.common.ratelimit.annotation.RedissonRateLimiters;
import com.goldman.distributed.common.ratelimit.config.RateLimitConfigProp;
import com.goldman.distributed.common.ratelimit.exception.RateLimitException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;

import java.nio.charset.StandardCharsets;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Bin
 * @date 2023/12/13
 */
@Slf4j
@Component
@Aspect
public class RedissionRateLimiterAspectHandler {

    private ConcurrentHashMap<String, RRateLimiter> limiters = new ConcurrentHashMap<>(16);

    @Autowired
    private RateLimiterKeyProvider rateLimiterKeyProvider;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RateLimitConfigProp prop;

    @Around(value = "@annotation(redissonRateLimiters)", argNames = "point, redissonRateLimiters")
    public Object around(ProceedingJoinPoint point, RedissonRateLimiters redissonRateLimiters) throws Throwable {

        if (prop.isOn()) {
            log.info("限流开启中...");
            RedissonRateLimiter[] limiters = redissonRateLimiters.value();
            for (RedissonRateLimiter redissonRateLimiter : limiters) {
                isAllow(point, redissonRateLimiter);
            }
        }
        return point.proceed();
    }

    private void isAllow(ProceedingJoinPoint point, RedissonRateLimiter redissonRateLimiter) {
        String key = rateLimiterKeyProvider.getKey(point, redissonRateLimiter);
        String uniqKey = getUniqueKey((MethodSignature) point.getSignature());
        key = StringUtils.isEmpty(key) ? uniqKey : uniqKey + "." + key;
        boolean tryAcquire;
        try {
            RRateLimiter limiter;
            if (limiters.get(key) == null) {
                limiter = redissonClient.getRateLimiter(key);
                limiter.setRate(redissonRateLimiter.mode(), redissonRateLimiter.rate(), redissonRateLimiter.rateInterval(),
                        redissonRateLimiter.intervalUnit());
                limiters.put(key, limiter);
            } else {
                limiter = limiters.get(key);
            }
            tryAcquire = limiter.tryAcquire();
        } catch (Exception ex) {
            log.error("限流,操作redisson异常", ex);
            //出现异常时，默认给过
            tryAcquire = true;
        }
        if (!tryAcquire) {
            log.error("限流模式开启:{},限流数量:{},限流时间间隔:{}", redissonRateLimiter.mode().toString(),
                    redissonRateLimiter.rate(), redissonRateLimiter.rateInterval());
            String interfaceName = String.format("%s.%s", point.getSignature().getDeclaringType().getCanonicalName(),
                    point.getSignature().getName());
            throw new RateLimitException(interfaceName + " request too frequently");
        }

    }

    private String getUniqueKey(MethodSignature signature) {
        return DigestUtils.md5DigestAsHex(String.format("%s.%s", signature.getDeclaringTypeName(),
                signature.getMethod().getName()).getBytes(StandardCharsets.UTF_8));
    }
}
